﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.IO;

namespace OCXS.OCXSServiceLibrary.Configuration {

    /// <summary>
    /// Responsible for loading the configuration and writing it.
    /// </summary>
    public class OCXSConfigurationManager {

        /// <summary>
        /// Name of the file to be loaded.
        /// </summary>
        protected string fileName;

        /// <summary>
        /// Result of the parsing process, holds the parsed variables from xml.
        /// </summary>
        protected VariableType[] parsedVariables;

        /// <summary>
        /// Result from the inverse parsing process to produce xml from a certain collection of types.
        /// </summary>
        protected string parsedXml;

        /// <summary>
        /// If this field is not null, parsing failed.
        /// </summary>
        protected Exception configurationManagerException;

        /// <summary>
        /// Gets the file name to be loaded.
        /// </summary>
        public string FileName {
            get { return this.fileName; }
        }

        /// <summary>
        /// Gets the parsed variables.
        /// </summary>
        public VariableType[] ParsedVariables {
            get { return this.parsedVariables; }
        }

        /// <summary>
        /// Gets the synthetized xml.
        /// </summary>
        public string ParsedXml {
            get { return this.parsedXml; }
        }

        /// <summary>
        /// Gets the possible exception after trying to parse.
        /// </summary>
        public Exception ConfigurationManagerException {
            get { return this.configurationManagerException; }
        }

        /// <summary>
        /// Default Constructor.
        /// </summary>
        public OCXSConfigurationManager() {
            this.fileName = "ocxs.config.xml";
            this.parsedVariables = null;
            this.parsedXml = null;
        }

        /// <summary>
        /// Instructs the configuration manager to parse the specified xml file.
        /// </summary>
        public OCXSConfigurationManager(string filename) : this() {
            this.fileName = filename;
            this.ParseXml();
        }

        /// <summary>
        /// Parses the xml file specified in the file name.
        /// </summary>
        private void ParseXml() {
            // Create an XML document instance.
            // The same instance of DOM is used through out this code; this 
            // may or may not be the actual case.
            XmlDocument doc = new XmlDocument();

            // Load the XML data from a file.
            // This code assumes that the XML file is in the same folder.
            doc.Load(this.fileName);

            // Navigating
            XmlNodeList vars = doc.GetElementsByTagName("Variable", "http://OCXS.org/Configuration");

            List<VariableType> parsedvars = new List<VariableType>();

            try {
                foreach (XmlNode v in vars) {
                    VariableType tempvar = new VariableType();

                    // Path
                    tempvar.VariablePath = (v.ChildNodes[0].FirstChild != null) ? v.ChildNodes[0].FirstChild.Value : null;
                    // Name
                    tempvar.VariableName = (v.ChildNodes[1].FirstChild != null) ? v.ChildNodes[1].FirstChild.Value : null;
                    // Description
                    tempvar.VariableDescription = (v.ChildNodes[2].FirstChild != null) ? v.ChildNodes[2].FirstChild.Value : null;
                    // VariableT
                    if (v.ChildNodes[3].FirstChild != null) {
                        this.ManageVariableTypeParse(ref tempvar, v);
                    } else {
                        // Default assignment
                        tempvar.VariableT = VariableTType.Bit;
                    }
                    // Measurement Unit
                    tempvar.VariableMU = (v.ChildNodes[4].FirstChild != null) ? v.ChildNodes[4].FirstChild.Value : null;
                    // Generator
                    this.ManageVariableGenerator(ref tempvar, v);
                    // Value
                    tempvar.VariableValue = (v.ChildNodes[6].FirstChild != null) ? v.ChildNodes[6].FirstChild.Value : null;

                    parsedvars.Add(tempvar);
                }
            } catch (Exception e) {
                this.configurationManagerException = e;
            }

            this.parsedVariables = parsedvars.ToArray();
        } /* ParseXml */

        /// <summary>
        /// Internal usage method, used to understand the type of variable in order to infer the 
        /// correct generator for the specified variable.
        /// </summary>
        /// <param name="tvar">Specified variable type.</param>
        /// <param name="node">Reference to the XML configuration node for the specified variable.</param>
        private void ManageVariableTypeParse(ref VariableType tvar, XmlNode node) {

            string val = node.ChildNodes[3].FirstChild.Value.ToLower();
            switch (val) {
            case "bit":
                tvar.VariableT = VariableTType.Bit;
                break;
            case "pressure":
                tvar.VariableT = VariableTType.Pressure;
                break;
            case "temperature":
                tvar.VariableT = VariableTType.Temperature;
                break;
            case "volume":
                tvar.VariableT = VariableTType.Volume;
                break;
            default:
                tvar.VariableT = VariableTType.Bit;
                break;
            }
            
        } /* ManageVariableType */

        /// <summary>
        /// Configuers the variable VariableGenerator settings.
        /// </summary>
        /// <param name="tvar">Specified variable type.</param>
        /// <param name="node">Reference to the XML configuration node for the specified variable.</param>
        private void ManageVariableGenerator(ref VariableType tvar, XmlNode node) {

            tvar.VariableGenerator = new VariableGeneratorType();

            switch (node.ChildNodes[5].ChildNodes.Count) {
            case 2: // User specified one between list or function
            case 3: // User specified both function and list --> defaults to function

                // User specified a generator (can be static list or function)
                if (node.ChildNodes[5].FirstChild.Name.ToLower().IndexOf("generatingfunction") != -1) {
                    // User specified a generating function
                    string val = node.ChildNodes[5].FirstChild.FirstChild.Value.ToLower();
                    switch (val) {
                    case "null":
                        tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.Null;
                        break;
                    case "sine":
                        tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.Sine;
                        break;
                    case "cosine":
                        tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.Cosine;
                        break;
                    case "ramp":
                        tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.Ramp;
                        break;
                    case "linear":
                        tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.Linear;
                        break;
                    case "squarewave":
                        tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.SquareWave;
                        break;
                    default:
                        tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.Null;
                        break;
                    }
                    tvar.VariableGenerator.GeneratingFunctionSpecified = true;
                    tvar.VariableGenerator.StaticValues = null;
                } else {
                    // User specified a list
                    int valuesNum = node.ChildNodes[5].FirstChild.ChildNodes.Count;

                    if (valuesNum == 0)
                        tvar.VariableGenerator.StaticValues = new float[] { 0 };
                    else {
                        tvar.VariableGenerator.StaticValues = new float[node.ChildNodes[5].FirstChild.ChildNodes.Count];
                        for (int i = 0; i < valuesNum; i++)
                            tvar.VariableGenerator.StaticValues[i] = 
                                int.Parse(node.ChildNodes[5].FirstChild.ChildNodes[i].FirstChild.Value);
                    }
                    tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.Null;
                    tvar.VariableGenerator.GeneratingFunctionSpecified = false;
                }

                if (node.ChildNodes[5].ChildNodes.Count == 2)
                    tvar.VariableGenerator.PeriodUpdate = int.Parse(node.ChildNodes[5].ChildNodes[1].FirstChild.Value);
                else
                    tvar.VariableGenerator.PeriodUpdate = int.Parse(node.ChildNodes[5].ChildNodes[2].FirstChild.Value);
                break;

            case 1:
            default:

                // User specified no generator, just the period ==> defaults to generating a Null function
                tvar.VariableGenerator.GeneratingFunction = GeneratingFunctionType.Null;
                tvar.VariableGenerator.GeneratingFunctionSpecified = false;
                tvar.VariableGenerator.StaticValues = null;
                tvar.VariableGenerator.PeriodUpdate = int.Parse(node.ChildNodes[5].FirstChild.FirstChild.Value);
                break;

            }

        } /* ManageVariableGenerator */

    }

} /* Configuration */
